{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2087bdc5",
   "metadata": {},
   "source": [
    "# 测试 `relay.qnn.simulated_dequantize`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a17eb21d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tvm\n",
    "from tvm import te\n",
    "import numpy as np\n",
    "from tvm import relay\n",
    "from tvm.contrib import graph_executor\n",
    "from tvm.runtime.vm import VirtualMachine\n",
    "from tvm.topi.nn.qnn import SQNN_DTYPE_TO_CODE\n",
    "\n",
    "\n",
    "def dequantize_test_driver(in_dtype, quant_args, axis, in_data):\n",
    "    \"\"\"\n",
    "    QNN反量化测试驱动函数\n",
    "\n",
    "    该函数创建一个Relay计算图，对输入数据执行反量化操作，并返回反量化结果。\n",
    "    主要用于生成参考结果，以便与模拟反量化结果进行比较。\n",
    "\n",
    "    参数:\n",
    "    in_dtype: str - 输入数据类型\n",
    "    quant_args: dict - 量化参数，包含in_zero_point和in_scale\n",
    "    axis: int - 量化轴\n",
    "    in_data: numpy.ndarray - 输入数据\n",
    "\n",
    "    返回:\n",
    "    numpy.ndarray - 反量化后的结果\n",
    "    \"\"\"\n",
    "    # 获取输入数据形状\n",
    "    shape = in_data.shape\n",
    "    # 创建Relay变量\n",
    "    input_data = relay.var(\"input_data\", shape=shape, dtype=in_dtype)\n",
    "    # 创建量化参数常量\n",
    "    input_zero_point = relay.const(quant_args[\"in_zero_point\"])\n",
    "    input_scale = relay.const(quant_args[\"in_scale\"])\n",
    "    # 创建反量化操作\n",
    "    dequantized_output = relay.qnn.dequantize(\n",
    "        input_data,\n",
    "        input_scale=input_scale,\n",
    "        input_zero_point=input_zero_point,\n",
    "        axis=axis,\n",
    "    )\n",
    "    # 创建Relay函数和IR模块\n",
    "    mod = relay.Function(relay.analysis.free_vars(dequantized_output), dequantized_output)\n",
    "    mod = tvm.IRModule.from_expr(mod)\n",
    "    dev = tvm.cpu(0)\n",
    "    target = \"llvm\"\n",
    "    with tvm.transform.PassContext(opt_level=3):\n",
    "        lib = relay.build(mod, target=target)\n",
    "    # 创建运行时模块\n",
    "    rt_mod = graph_executor.GraphModule(lib[\"default\"](dev))\n",
    "    rt_mod.set_input(input_data=in_data)\n",
    "    rt_mod.run()\n",
    "    # 获取输出结果\n",
    "    res = rt_mod.get_output(0).numpy()\n",
    "    return res\n",
    "\n",
    "\n",
    "def build_simulated_dequantize(input_data, scale, zp, dtype, axis=-1):\n",
    "    \"\"\"\n",
    "    构建QNN模拟反量化的虚拟机执行环境\n",
    "\n",
    "    该函数创建一个包含模拟反量化操作的Relay计算图，编译它，并返回一个虚拟机实例，\n",
    "    用于执行模拟反量化操作。\n",
    "\n",
    "    参数:\n",
    "    input_data: relay.Var - 输入数据变量\n",
    "    scale: relay.Var - 缩放因子变量\n",
    "    zp: relay.Var - 零点变量\n",
    "    dtype: relay.Var - 数据类型变量\n",
    "    axis: int - 量化轴，默认为-1（最后一个维度）\n",
    "\n",
    "    返回:\n",
    "    tvm.runtime.vm.VirtualMachine - 虚拟机实例\n",
    "    \"\"\"\n",
    "    # 创建模拟反量化操作\n",
    "    sim_q = relay.qnn.simulated_dequantize(\n",
    "        input_data,\n",
    "        scale,\n",
    "        zp,\n",
    "        axis=axis,\n",
    "        in_dtype=dtype,\n",
    "    )\n",
    "    # 创建IR模块\n",
    "    mod = tvm.IRModule.from_expr(sim_q)\n",
    "    # 编译模型（优化级别3）\n",
    "    with tvm.transform.PassContext(opt_level=3):\n",
    "        vm_exec = relay.vm.compile(mod, \"llvm\", params=None)\n",
    "    # 创建虚拟机实例\n",
    "    vm = VirtualMachine(vm_exec, tvm.cpu(0))\n",
    "    return vm\n",
    "\n",
    "\n",
    "def verify_simulated_dequantize_simple(dtype):\n",
    "    \"\"\"\n",
    "    验证简单QNN模拟反量化功能\n",
    "\n",
    "    该函数通过比较常规反量化与模拟反量化的结果，验证模拟反量化功能的正确性。\n",
    "\n",
    "    参数:\n",
    "    dtype: str - 输入数据类型（\"uint8\", \"int8\", \"int32\"等）\n",
    "    \"\"\"\n",
    "    # 生成随机测试数据\n",
    "    data = np.random.uniform(low=-128, high=127, size=[2, 5]).astype(dtype)\n",
    "    # 转换为float32用于模拟反量化\n",
    "    data_fp = data.astype(\"float32\")\n",
    "    # 设置量化参数\n",
    "    scale_np = np.float32(0.5)\n",
    "    zp_np = np.int32(127)\n",
    "    # 获取数据类型代码\n",
    "    dtype_np = np.int32(SQNN_DTYPE_TO_CODE[dtype])\n",
    "    # 准备反量化测试参数\n",
    "    quant_args = {\"in_zero_point\": zp_np, \"in_scale\": scale_np}\n",
    "    # 执行常规反量化获取参考结果\n",
    "    dq_out = dequantize_test_driver(\n",
    "        in_dtype=dtype,\n",
    "        quant_args=quant_args,\n",
    "        axis=-1,\n",
    "        in_data=data,\n",
    "    )\n",
    "    # 创建Relay变量\n",
    "    input_data = relay.var(\"input_data\", shape=data.shape, dtype=\"float32\")\n",
    "    scale = relay.var(\"scale\", shape=[])\n",
    "    zp = relay.var(\"zp\", shape=[], dtype=\"int32\")\n",
    "    dtype = relay.var(\"dtype\", shape=[], dtype=\"int32\")\n",
    "    # 构建模拟反量化虚拟机\n",
    "    vm = build_simulated_dequantize(input_data, scale, zp, dtype)\n",
    "    # 执行模拟反量化\n",
    "    sim_dq_out = vm.invoke(\"main\", input_data=data_fp, scale=scale_np, zp=zp_np, dtype=dtype_np)\n",
    "    # 比较结果\n",
    "    np.testing.assert_allclose(sim_dq_out.numpy(), dq_out, rtol=1e-5)\n",
    "\n",
    "\n",
    "def test_simulated_dequantize():\n",
    "    \"\"\"\n",
    "    QNN模拟反量化功能主测试函数\n",
    "\n",
    "    该函数测试不同数据类型（uint8、int8、int32）的模拟反量化功能。\n",
    "    \"\"\"\n",
    "    # 测试无符号8位整数反量化\n",
    "    verify_simulated_dequantize_simple(\"uint8\")\n",
    "    # 测试有符号8位整数反量化\n",
    "    verify_simulated_dequantize_simple(\"int8\")\n",
    "    # 测试有符号32位整数反量化\n",
    "    verify_simulated_dequantize_simple(\"int32\")\n",
    "\n",
    "\n",
    "def test_dynamic_channels():\n",
    "    \"\"\"\n",
    "    测试动态通道QNN模拟反量化功能\n",
    "\n",
    "    该函数测试模拟反量化在不重新编译的情况下，支持标量和每通道量化参数的能力。\n",
    "    先使用标量参数测试，然后不重新编译虚拟机，直接使用每通道参数测试。\n",
    "    \"\"\"\n",
    "    # 生成随机测试数据\n",
    "    data = np.random.uniform(low=-64, high=64, size=[2, 5]).astype(\"int8\")\n",
    "    data_fp = data.astype(\"float32\")\n",
    "    # 测试标量量化参数\n",
    "    scale_np = np.asarray([0.5]).astype(\"float32\")\n",
    "    zp_np = np.asarray([0]).astype(\"int32\")\n",
    "    dtype_np = np.int32(SQNN_DTYPE_TO_CODE[\"int8\"])\n",
    "    quant_args = {\"in_zero_point\": zp_np[0], \"in_scale\": scale_np[0]}\n",
    "    # 获取标量参数反量化参考结果\n",
    "    dq_out = dequantize_test_driver(\n",
    "        in_dtype=\"int8\",\n",
    "        quant_args=quant_args,\n",
    "        axis=0,\n",
    "        in_data=data,\n",
    "    )\n",
    "    # 创建具有动态形状的Relay变量\n",
    "    input_data = relay.var(\"input_data\", shape=data.shape, dtype=\"float32\")\n",
    "    scale = relay.var(\"scale\", shape=[relay.Any()], dtype=\"float32\")\n",
    "    zp = relay.var(\"zp\", shape=[relay.Any()], dtype=\"int32\")\n",
    "    dtype = relay.var(\"dtype\", shape=[], dtype=\"int32\")\n",
    "    # 构建模拟反量化虚拟机\n",
    "    vm = build_simulated_dequantize(input_data, scale, zp, dtype, axis=0)\n",
    "    # 使用标量参数执行模拟反量化\n",
    "    sim_dq_out = vm.invoke(\"main\", input_data=data_fp, scale=scale_np, zp=zp_np, dtype=dtype_np)\n",
    "    np.testing.assert_allclose(sim_dq_out.numpy(), dq_out, rtol=1e-5)\n",
    "\n",
    "    # 测试每通道量化参数（不重新编译虚拟机）\n",
    "    scale_np = np.array([0.5, 0.25]).astype(\"float32\")\n",
    "    zp_np = np.array([127, 123]).astype(\"int32\")\n",
    "\n",
    "    # 获取每通道参数反量化参考结果\n",
    "    quant_args = {\"in_zero_point\": zp_np, \"in_scale\": scale_np}\n",
    "    dq_out = dequantize_test_driver(\n",
    "        in_dtype=\"int8\",\n",
    "        quant_args=quant_args,\n",
    "        axis=0,\n",
    "        in_data=data,\n",
    "    )\n",
    "    # 不重新编译，直接使用每通道参数执行模拟反量化\n",
    "    sim_dq_out = vm.invoke(\"main\", input_data=data_fp, scale=scale_np, zp=zp_np, dtype=dtype_np)\n",
    "    np.testing.assert_allclose(sim_dq_out.numpy(), dq_out, rtol=1e-5)\n",
    "\n",
    "\n",
    "def test_dynamic_dtype():\n",
    "    \"\"\"\n",
    "    测试动态数据类型QNN模拟反量化功能\n",
    "\n",
    "    该函数测试模拟反量化在不重新编译的情况下，支持不同数据类型的能力。\n",
    "    先使用uint8数据类型测试，然后不重新编译虚拟机，直接使用int8数据类型测试。\n",
    "    \"\"\"\n",
    "    # 生成uint8随机测试数据\n",
    "    data = np.random.uniform(low=0, high=255, size=[2, 5]).astype(\"uint8\")\n",
    "    data_fp = data.astype(\"float32\")\n",
    "    # 设置量化参数\n",
    "    scale_np = np.asarray([0.5]).astype(\"float32\")\n",
    "    zp_np = np.asarray([127]).astype(\"int32\")\n",
    "    dtype_np = np.int32(SQNN_DTYPE_TO_CODE[\"uint8\"])\n",
    "    quant_args = {\"in_zero_point\": zp_np[0], \"in_scale\": scale_np[0]}\n",
    "    # 获取uint8反量化参考结果\n",
    "    dq_out = dequantize_test_driver(\n",
    "        in_dtype=\"uint8\",\n",
    "        quant_args=quant_args,\n",
    "        axis=-1,\n",
    "        in_data=data,\n",
    "    )\n",
    "    # 创建具有动态形状的Relay变量\n",
    "    input_data = relay.var(\"input_data\", shape=data.shape, dtype=\"float32\")\n",
    "    scale = relay.var(\"scale\", shape=[relay.Any()], dtype=\"float32\")\n",
    "    zp = relay.var(\"zp\", shape=[relay.Any()], dtype=\"int32\")\n",
    "    dtype = relay.var(\"dtype\", shape=[], dtype=\"int32\")\n",
    "    # 构建模拟反量化虚拟机\n",
    "    vm = build_simulated_dequantize(input_data, scale, zp, dtype)\n",
    "    # 使用uint8数据类型执行模拟反量化\n",
    "    sim_dq_out = vm.invoke(\"main\", input_data=data_fp, scale=scale_np, zp=zp_np, dtype=dtype_np)\n",
    "    np.testing.assert_allclose(sim_dq_out.numpy(), dq_out, rtol=1e-5)\n",
    "\n",
    "    # 测试int8数据类型（不重新编译虚拟机）\n",
    "    data = np.random.uniform(low=0, high=255, size=[2, 5]).astype(\"int8\")\n",
    "    data_fp = data.astype(\"float32\")\n",
    "    # 获取int8反量化参考结果\n",
    "    dq_out = dequantize_test_driver(\n",
    "        in_dtype=\"int8\",\n",
    "        quant_args=quant_args,\n",
    "        axis=-1,\n",
    "        in_data=data,\n",
    "    )\n",
    "    # 不重新编译，直接使用int8数据类型执行模拟反量化\n",
    "    dtype_np = np.int32(SQNN_DTYPE_TO_CODE[\"int8\"])\n",
    "    sim_dq_out = vm.invoke(\"main\", input_data=data_fp, scale=scale_np, zp=zp_np, dtype=dtype_np)\n",
    "    np.testing.assert_allclose(sim_dq_out.numpy(), dq_out, rtol=1e-5)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    # 执行模拟反量化功能测试\n",
    "    test_simulated_dequantize()\n",
    "    # 执行动态通道测试\n",
    "    test_dynamic_channels()\n",
    "    # 执行动态数据类型测试\n",
    "    test_dynamic_dtype()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6c2d390",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "aix",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
